#include "../SypEngine.h"

#ifdef CHECKMEM
#include "../mmgr.h"
#endif
using namespace Syp;

GPUSkinning::GPUSkinning():Pass("GPUSkinning","Applies Skinning in hardware to a Dynamic IndexedMesh")
{
	if(Renderer::getRenderer().getHardwareCpbRef().VertexShaders)
	{
		VertexShader* vs = new VertexShader("Shaders/Skinning.vert");

		vs->compileShader();

		ShaderProgram* sp = new ShaderProgram("Skinning");
		sp->addShader(vs);
		sp->linkShaders();
		useShaderProgram(sp);
	}	

}

bool GPUSkinning::runnableOnHardware()
{
	//return false;
	return Renderer::getRenderer().getHardwareCpbRef().VertexShaders;//return if Vertex Shaders are supported
}

void GPUSkinning::destroy()
{
	delete this;
}

void GPUSkinning::preDraw()
{
	MeshInstance* m = Renderer::getRenderer().getCurrentMI();
	IndexedMesh* im = static_cast<IndexedMesh*>(Renderer::getRenderer().getCurrentMesh());
	if(im->isSkinned() && im->getSkeleton())
	{///Check to see if MI not been transformed by one of it's other Renderable. If it has, m->AnimTime does not need updating
		if(m->animate)
		{//dont progress time if not animated
			if(m->lastTransformedOn != SypEngine::getSypEngine().getCurrFrameTime())
			{
				//Get time elapsed
				float timeElapsed = static_cast<float>(SypEngine::getSypEngine().getElapsedTime());
				//update 
				//(Mesh's relative time in frames)
				m->AnimTime += (timeElapsed * m->speed);
				m->lastTransformedOn = SypEngine::getSypEngine().getCurrFrameTime();

			} 
			//If animation is over, restart it
			//Possibly add other modes in future
			if(m->currAnimSet == -1 && m->AnimTime > im->getSkeleton()->getAnimLength())
			{
				//SUppose our time is 306 and animlength is 100, our outstanding is 206 frames. 
				// 306 / 100 is 3
				// 306 - (3* 100) = 6

				m->AnimTime = m->AnimTime - (static_cast<int>(m->AnimTime / im->getSkeleton()->getAnimLength() ) * im->getSkeleton()->getAnimLength());			
			}
			else if( m->currAnimSet != -1 && m->AnimTime > im->getSkeleton()->getAnimSet(m->currAnimSet).end )
			{
				float start = im->getSkeleton()->getAnimSet(m->currAnimSet).start;
				float end = im->getSkeleton()->getAnimSet(m->currAnimSet).end;
				m->AnimTime = start + (m->AnimTime - end);
			}

		}
		//If mesh is already at this frame, return and avoid transforming
		if(im->getCurrAnimTrans() != m->AnimTime)
		{


			//---------------------------Begin actual Transforming-----------------------

			float interpolation = 0.0f;//float defining how much to interpolate by
			//Mesh is now being transformed to this time in seconds
			im->setCurrAnimTrans(m->AnimTime);

			PInt numBones = im->getSkeleton()->getNumBones();



			PInt boneCounter = 0;
			//for every bone
			while(boneCounter < numBones)
			{	          			
				im->transformBone(im->getSkeleton()->getBoneRef(boneCounter));
				//next bone
				++boneCounter;
			}
		}
		//Now that we have our matrices, we do the mesh skinning based on our bone position/orientations. Since this can be done in the hardware, we let the renderer decide on how it should be done.

		std::vector<Bone*>& bones = im->getSkeleton()->getBonesRef();
		std::vector<Bone*>::iterator beg = bones.begin();
		std::vector<Bone*>::iterator end = bones.end();
		FloatArray matrices;
		matrices.resize( bones.size() * 16 );//4X3 matrice for each bone
		FloatArray::iterator ins = matrices.begin();
		String uniformName = "boneMat";
		PInt boneCounter = 0;
		while( beg != end )
		{
			std::copy( (*beg)->getMatrix(), (*beg)->getMatrix() + 16 , ins );
			++beg;
			std::advance( ins, 16);
		}
		Renderer::getRenderer().setUniformMatrix4f(uniformName, static_cast<PInt>(bones.size()), &matrices[0]);//we need 3 vec4s to make a 4X3 matrix
	}
	String attrib = "boneIds";
	Renderer::getRenderer().bindMeshAttribBuffers( im, false, true );
	//Renderer::getRenderer().setAttributei( attrib, 4,false, 0, &(im->getAttribDatapbRef()[0]) );//NO VBO
	Renderer::getRenderer().setAttributei( attrib, 4,false, 0, 0 );//VBO

	attrib = "boneWeights";
	Renderer::getRenderer().bindMeshAttribBuffers( im, true, false );
	//Renderer::getRenderer().setAttributef( attrib ,4,false, 0, &(im->getAttribDataRef()[0]) );
	Renderer::getRenderer().setAttributef( attrib ,4,false, 0, 0 );
}

void GPUSkinning::AnimateBoneMesh(IndexedMesh* m)
{



}

void GPUSkinning::postDraw()
{
	String attrib = "boneIds";
	Renderer::getRenderer().unSetAttribute(attrib);
	attrib = "boneWeights";
	Renderer::getRenderer().unSetAttribute(attrib);

}
